#include "gkt_board.h"
#include "gkt_debug.h"
#include "gkt_flash.h"
#include "gkt_rtc.h"
#include "gkt_os.h"
#include "gkt_malloc.h"
#include "gkt_flash_partition.h"
#include "gkt_record_unlock.h"

#if !defined(GKT_CONFIG_RECORD_UNLOCK_DISABLE) \
	|| !GKT_CONFIG_RECORD_UNLOCK_DISABLE

typedef struct _record_unlock_store_item {
	uint32_t	user_ids[2];
	uint32_t	dtu;
} record_unlock_store_item_s;
#define RECORD_UNLOCK_STORE_ITEM_SIZE	sizeof(record_unlock_store_item_s)

#ifdef GKT_CONFIG_GKT_RECORD_UNLOCK_RECYCLE_ITEM_NUMS
#define RECORD_UNLOCK_RECYCLE_ITEM_NUMS	\
	GKT_CONFIG_GKT_RECORD_UNLOCK_RECYCLE_ITEM_NUMS
#else
#define RECORD_UNLOCK_RECYCLE_ITEM_NUMS	16
#endif

#define RECORD_UNLOCK_MAGIC	0x0C2E3A1C
struct _record_unlock_info {
	uint32_t	magic;
	uint32_t	free_top_index;
	uint32_t	free_end_index;
	uint32_t	used_start_index;
};
static __RETMEM_USR struct _record_unlock_info s_record_unlock_info;

struct _record_unlock {
	const gkt_flash_partition_info_s	*c_flash_info;
	union {
		const record_unlock_store_item_s	*c_store_items;
		record_unlock_store_item_s	*v_store_items;
	};

	uint32_t	item_available_nums;
	uint32_t	item_index_start;	/* v_store_items start index */
};
static struct _record_unlock s_record_unlock;

#define RECORD_UNLOCK_V_ITEMS_MAXNUMS	4
#define RECORD_UNLOCK_V_ITEMS_MAXSIZE	\
	(sizeof(record_unlock_store_item_s) * RECORD_UNLOCK_V_ITEMS_MAXNUMS)

static const record_unlock_store_item_s *
	record_unlock_get_item(uint32_t item_index)
{
	const record_unlock_store_item_s *c_item;
	uint32_t item_index_start;
	int retval;

	if (!s_record_unlock.c_flash_info->read_map_addr) {
		if (!s_record_unlock.v_store_items) {
			s_record_unlock.v_store_items = (record_unlock_store_item_s *)
				gkt_malloc(RECORD_UNLOCK_V_ITEMS_MAXSIZE);
			if (!s_record_unlock.v_store_items) {
				gkt_error("record_unlock_get_item: alloc failed.\n");
				return NULL;
			}

			s_record_unlock.item_index_start = ~0;
		}

		if ((item_index < s_record_unlock.item_index_start)
			|| (item_index >= (s_record_unlock.item_index_start + RECORD_UNLOCK_V_ITEMS_MAXNUMS)))
		{
			item_index_start = item_index;
			if ((item_index_start + RECORD_UNLOCK_V_ITEMS_MAXNUMS) >= s_record_unlock.item_available_nums)
				item_index_start = s_record_unlock.item_available_nums - RECORD_UNLOCK_V_ITEMS_MAXNUMS;

			retval = gkt_flash_partition_read(s_record_unlock.c_flash_info->id,
							sizeof(record_unlock_store_item_s) * item_index_start, 
							RECORD_UNLOCK_V_ITEMS_MAXSIZE, s_record_unlock.v_store_items);
			if (retval != GKT_SUCCESS) {
				s_record_unlock.item_index_start = ~0;
				gkt_error("record_unlock_get_item: read flash failed %d\n", retval);
				return NULL;
			}

			s_record_unlock.item_index_start = item_index_start;
		}

		c_item = (const record_unlock_store_item_s *)(s_record_unlock.v_store_items 
					+ (item_index - s_record_unlock.item_index_start));
	}
	else
		c_item = &s_record_unlock.c_store_items[item_index];

	return c_item;
}

static void record_unlock_init_info(void)
{
	const record_unlock_store_item_s *c_item;
	uint32_t item_index;

	s_record_unlock_info.magic = 0;
	s_record_unlock_info.free_top_index = s_record_unlock.item_available_nums;

	item_index = 0;
	do {
		c_item = record_unlock_get_item(item_index);
		if (c_item && ((uint32_t)~0 == c_item->user_ids[0])) {
			if (item_index < s_record_unlock_info.free_top_index)
				s_record_unlock_info.free_top_index = item_index;	/* first free item */
		}
		else if (s_record_unlock_info.free_top_index < item_index)
			break;	/* used item follows free items */
		item_index++;
	} while (item_index < s_record_unlock.item_available_nums);

	s_record_unlock_info.free_end_index = item_index;

	if (s_record_unlock_info.free_end_index < s_record_unlock.item_available_nums)
		s_record_unlock_info.used_start_index = s_record_unlock_info.free_end_index;
	else
		s_record_unlock_info.used_start_index = 0;

	s_record_unlock_info.magic = RECORD_UNLOCK_MAGIC;
}

/* recycle a part of used items */
static int record_unlock_recycle_items(void)
{
	uint32_t new_free_top_index, new_free_end_index;
	uint32_t offset, size, try_times;
	int retval;

	gkt_info("record_unlock_recycle: free_index - %u @ %u\n", 
				s_record_unlock_info.free_top_index, 
				s_record_unlock_info.free_end_index);

	if (s_record_unlock_info.free_end_index < s_record_unlock.item_available_nums) {
		new_free_top_index = s_record_unlock_info.free_end_index;
		new_free_end_index = s_record_unlock_info.free_end_index + RECORD_UNLOCK_RECYCLE_ITEM_NUMS;
	}
	else {
		new_free_top_index = 0;
		new_free_end_index = RECORD_UNLOCK_RECYCLE_ITEM_NUMS;
	}
	if (new_free_end_index > s_record_unlock.item_available_nums)
		new_free_end_index = s_record_unlock.item_available_nums;

	offset = new_free_top_index * RECORD_UNLOCK_STORE_ITEM_SIZE;
	size = (new_free_end_index - new_free_top_index) * RECORD_UNLOCK_STORE_ITEM_SIZE;
	gkt_info("record_unlock_recycle: erase - 0x%x @ 0x%x\n", offset, size);

	try_times = 3;
	s_record_unlock_info.magic = 0;
	do {
		retval = gkt_flash_partition_erase(s_record_unlock.c_flash_info->id, offset, size);
		if (GKT_SUCCESS == retval) {
			s_record_unlock_info.free_top_index = new_free_top_index;
			s_record_unlock_info.free_end_index = new_free_end_index;
			s_record_unlock_info.used_start_index = new_free_end_index;
			if (s_record_unlock_info.used_start_index == s_record_unlock.item_available_nums)
				s_record_unlock_info.used_start_index = 0;
			s_record_unlock_info.magic = RECORD_UNLOCK_MAGIC;

			break;
		}
	} while (--try_times);

	return retval;
}

int gkt_record_unlock_add(uint32_t user_ids[2], uint32_t nums)
{
	struct _record_unlock_store_item v_store_item;
	gkt_date_time_s dt;
	uint32_t item_index, offset;
	int retval;

	gkt_trace("record_unlock_add: (%u) user_ids(0x%x)(0x%x)\n", 
		nums, user_ids[0], user_ids[1]);

	if (!s_record_unlock.item_available_nums)
		return GKT_ENOTINIT;
	else if (s_record_unlock_info.magic != RECORD_UNLOCK_MAGIC)
		record_unlock_init_info();
	
	if (s_record_unlock_info.free_top_index 
		== s_record_unlock_info.free_end_index) 
	{
		retval = record_unlock_recycle_items();
		if (retval != GKT_SUCCESS)
			return retval;
	}

	gkt_rtc_get_time(&dt);
	v_store_item.user_ids[0] = user_ids[0];
	if ((nums > 1) && (user_ids[1] != 0))
		v_store_item.user_ids[1] = user_ids[1];
	else
		v_store_item.user_ids[1] = 0;
	v_store_item.dtu = GKT_DTS_2_DTU(dt);

	item_index = s_record_unlock_info.free_top_index++;
	offset = RECORD_UNLOCK_STORE_ITEM_SIZE * item_index;
	retval = gkt_flash_partition_write(s_record_unlock.c_flash_info->id,
				offset, RECORD_UNLOCK_STORE_ITEM_SIZE, 
				&v_store_item, 0);
	gkt_trace("record_unlock_add: item_index(%u), result(%d)\n", 
		item_index, retval);

	return retval;
}

uint32_t gkt_record_unlock_get_nums(void)
{
	uint32_t used_nums = 0;

	if (s_record_unlock.item_available_nums > 0) {
		if (s_record_unlock_info.magic != RECORD_UNLOCK_MAGIC)
			record_unlock_init_info();
		
		/* free nums */
		used_nums = s_record_unlock_info.free_end_index 
						- s_record_unlock_info.free_top_index;

		/* total nums - free nums */
		used_nums = s_record_unlock.item_available_nums - used_nums;
	}

	return used_nums;
}

int gkt_record_unlock_get_item(uint32_t index, 
				gkt_record_unlock_item_s *record)
{
	const struct _record_unlock_store_item *c_store_item;
	uint32_t item_index;
	int retval;

	if (!s_record_unlock.item_available_nums)
		return GKT_ENOTINIT;
	else if (s_record_unlock_info.magic != RECORD_UNLOCK_MAGIC)
		record_unlock_init_info();

	if ((index < s_record_unlock.item_available_nums) && record) {
		item_index = s_record_unlock_info.used_start_index + index;
		if (item_index >= s_record_unlock.item_available_nums)
			item_index -= s_record_unlock.item_available_nums;

		c_store_item = record_unlock_get_item(item_index);
		if (c_store_item 
			&& (c_store_item->user_ids[0] != (uint32_t)~0)) 
		{
			record->user_ids[0] = c_store_item->user_ids[0];
			record->user_ids[1] = c_store_item->user_ids[1];
			GKT_DTU_2_DTS(record->dt, c_store_item->dtu);

			retval = GKT_SUCCESS;
		}
		else
			retval = GKT_ENOTEXIST;
	}
	else
		retval = GKT_EPARAM;

	return retval;
}

int gkt_record_unlock_init(void)
{
	if (!s_record_unlock.item_available_nums) {
		s_record_unlock.c_flash_info = gkt_flash_partition_get_info("RecordUnlock");
		if (!s_record_unlock.c_flash_info) {
			gkt_error("gkt_record_unlock_init: get partition info failed\n");
			return GKT_ENOTEXIST;
		}

		if (s_record_unlock.c_flash_info->read_map_addr) {
			s_record_unlock.c_store_items = (const struct _record_unlock_store_item *)
							s_record_unlock.c_flash_info->read_map_addr;
		}
		s_record_unlock.item_index_start = ~0;
		s_record_unlock.item_available_nums = s_record_unlock.c_flash_info->size / RECORD_UNLOCK_STORE_ITEM_SIZE;
	}

	return GKT_SUCCESS;
}

void gkt_record_unlock_reset(void)
{
	int retval;

	gkt_info("record_unlock_reset: ...\n");

	s_record_unlock_info.magic = 0;
	s_record_unlock_info.free_top_index = 0;
	s_record_unlock_info.free_end_index = s_record_unlock.item_available_nums;
	s_record_unlock_info.used_start_index = 0;

	if (s_record_unlock.c_flash_info) {
		/* erase flash sectors of record_unlock items */
		retval = gkt_flash_partition_erase_all(s_record_unlock.c_flash_info->id);
		if (GKT_SUCCESS == retval)
			s_record_unlock_info.magic = RECORD_UNLOCK_MAGIC;
	}
}

#else

int gkt_record_unlock_add(uint32_t user_ids[2], uint32_t nums)
{
	return GKT_ENOTSUPPORT;
}

uint32_t gkt_record_unlock_get_nums(void)
{
	return 0;
}

int gkt_record_unlock_get_item(uint32_t index, 
				gkt_record_unlock_item_s *record)
{
	return GKT_ENOTSUPPORT;
}

void gkt_record_unlock_reset(void)
{
	return;
}

int gkt_record_unlock_init(void)
{
	return GKT_ENOTSUPPORT;
}

#endif

